package com.gitee.l0km.codegen.base.generator;

import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.generic.EscapeTool;
import org.apache.velocity.tools.generic.SortTool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitee.l0km.codegen.annotations.CodegenDefaultvalue;
import com.gitee.l0km.codegen.annotations.CodegenInvalidValue;
import com.gitee.l0km.codegen.annotations.CodegenLength;
import com.gitee.l0km.codegen.annotations.CodegenRequired;
import com.gitee.l0km.codegen.base.AbstractSchema;
import com.gitee.l0km.codegen.base.CreateInterfaceSourceConstants;
import com.gitee.l0km.codegen.base.DuplicatedConstants;
import com.gitee.l0km.com4j.base.ClassResourceUtils;
import com.gitee.l0km.com4j.cli.Context;
import com.gitee.l0km.javadocreader.JavadocReader;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

/**
 * @author guyadong
 *
 */
public abstract class Generator implements GeneratorConstants, DuplicatedConstants, CreateInterfaceSourceConstants {

	protected static final Logger logger = LoggerFactory.getLogger(Generator.class);
	private static final String VM_REG= ".+\\.vm$";
	private VelocityContext velocityContext = null;
	private VelocityEngine velocityEngine = null;
	private CodeWriter codeWriter = null;
	private GeneratorConfiguration config = null;

	public Generator() {
	}
	public Generator generate() {
		return generate(config.getTemplateFolder());
	}
	public Generator multiGenerate() {
		for(Entry<String, List<? extends AbstractSchema>> entry : generateTask().entrySet()){
			String taskFolder = entry.getKey();
			String path = new File(config.getTemplateFolder(),taskFolder).getPath();
			List<? extends AbstractSchema> sourceInfoList = entry.getValue();
			if(sourceInfoList.isEmpty()){
				generate(path);
			}else{
				for(AbstractSchema sourceInfo : sourceInfoList){
					velocityContext.put(SOURCE_INFO, sourceInfo);
					generate(path);
				}		
			}
		}
		return this;
	}
	private Generator generate(String taskPath) {
		try {
			beforeGenerate(velocityContext, taskPath);
			try{
				File path = new File(config.getTemplateRoot(), taskPath);
				if(config.useClasspathResourceLoader()){
					if(taskPath.endsWith(VM_SUFFIX)){
						generateFile(ClassResourceUtils.normalizePath(path.getPath(),false));	
					}else {
						File folder = new File(taskPath);
						for (String name : getTemplateNames(taskPath)){
							generateFile(ClassResourceUtils.normalizePath(new File(folder, name).getPath(),false));
						}
					}
				}else	if (path.isFile()) {
					generateFile(path.getPath());
				} 
			}finally{
				afterGenerate(velocityContext, taskPath);
			}
		
		} catch (ResourceNotFoundException e) {
			logger.error(e.toString());
		} catch (ParseErrorException e) {
			logger.error(e.toString());
		} catch (MethodInvocationException e) {
			logger.error(e.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
		}
		return this;
	}
	/**
	 * 判断{@code templateName}是否为被排除的模板文件
	 * @param templateName
	 * @return true if excluded,otherwise false
	 */
	private boolean isExcludedTmpl(final String templateName){
		return null == templateName ? false : Iterables.tryFind(config.getExcludeVms(), new Predicate<String>() {
			@Override
			public boolean apply(String input) {
				return templateName.endsWith(input);
			}
		}).isPresent();
	}
	/**
	 * 判断{@code templateName}是否为被排除的模板文件
	 * @param templateName
	 * @return true if excluded,otherwise false
	 */
	private boolean isIncludedTmpl(final String templateName){
		return null == templateName ? false : Iterables.tryFind(config.getIncludeVms(), new Predicate<String>() {
			@Override
			public boolean apply(String input) {
				return templateName.endsWith(input);
			}
		}).isPresent();
	}
	
	/**
	 * 判断{@code templateName}是否要求生成
	 * @param templateName
	 * @return true if excluded,otherwise false
	 */
	private boolean isNeedGenerate(final String templateName){
		if(null == templateName){
			return false;
		}
		if(!config.getIncludeVms().isEmpty()){
			return isIncludedTmpl(templateName);
		}
		if(!config.getExcludeVms().isEmpty()){
			return !isExcludedTmpl(templateName);
		}
		return true;
	}
	/**
	 * 执行{@code templateName}对指定的模板生成代码 
	 * @param templateName
	 * @throws ResourceNotFoundException
	 * @throws ParseErrorException
	 * @throws MethodInvocationException
	 * @throws Exception
	 */
	private void generateFile(String templateName) throws ResourceNotFoundException, ParseErrorException,
			MethodInvocationException, Exception {
		if(!isNeedGenerate(templateName)){
			return;
		}
		StringWriter writer = new StringWriter();
		// 复位标记
		codeWriter.setSaveCurrentFile(true);
		beforeGenerateFile(velocityContext, templateName);
		try{
			logger.info("GENERATING:{}", templateName);
			velocityContext.put("template", templateName.replace('\\', '/'));
			velocityEngine.mergeTemplate(templateName, "UTF-8", velocityContext, writer);
			logger.debug(writer.toString());
			if (null == codeWriter){
				throw new IllegalArgumentException("the member codeWriter not initialized");
			}
			codeWriter.write(writer.toString());
		}finally{
			afterGenerateFile(velocityContext, templateName);
		}
	}

	protected String getCmdLineSyntax() {
		return String.format("run%s [options]", this.getClass().getSimpleName());
	}

	protected abstract GeneratorConfiguration getGeneratorConfiguration();

	protected abstract Options getOptions();

	private final List<String> getTemplateNames(String subFolder) throws FileNotFoundException {
		File templateFolder = new File(new File(config.getTemplateRoot()), subFolder);
		if(config.useClasspathResourceLoader()){
			return getResourceTemplates(templateFolder.getPath());
		}else{
			return getFileTemplates(templateFolder.getPath());
		}
	}

	private static final List<String> getFileTemplates(String folder) {
		File templateFolder = new File(folder);
		if (!templateFolder.exists()){
			logger.info("SKIP:not found template folder:{},",templateFolder.getAbsolutePath());
			return Collections.emptyList();
		}			
		File[] files = templateFolder.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return pathname.isFile() && pathname.getName().matches(VM_REG);
			}
		});
		return Lists.transform(Arrays.asList(files), new Function<File,String>(){
			@Override
			public String apply(File input) {
				return input.getName();
			}});
	}
	private static final List<String> getResourceTemplates(String folder){
		if (!ClassResourceUtils.resourceExist(Generator.class, folder)){
			logger.info("SKIP:not found resource template folder:{},",folder);
			return Collections.emptyList();
		}
		return ClassResourceUtils.getFilesUnchecked(Generator.class, folder, new ClassResourceUtils.FileFilter() {
			@Override
			public boolean accept(String filename) {
				return filename.matches(VM_REG);
			}
		});
	}
	public Generator initEngine() {
		Context context = createEngineContext();
		// 添加公共内置对象
		context.setProperty(GENERATOR_TOOL, GeneratorUtils.class);
		context.setProperty(JAVADOC_READER,JavadocReader.class);
		context.setProperty(JAVA_CLASS, Class.class);		
		context.setProperty(INLINE_SOURCE, false);
		context.setProperty(CODEGEN_REQUIRED_CLASS,CodegenRequired.class);
		context.setProperty(CODEGEN_INVALIDVALUE_CLASS,CodegenInvalidValue.class);
		context.setProperty(CODEGEN_DEFAULTVALUE_CLASS,CodegenDefaultvalue.class);
		context.setProperty(CODEGEN_LENGTH_CLASS,CodegenLength.class);

		velocityEngine = new VelocityEngine();
		if(config.useClasspathResourceLoader()){
			// 从classpath加载模板
			velocityEngine.setProperty(VelocityEngine.RESOURCE_LOADER, "class");
			velocityEngine.setProperty("class.resource.loader.description", "Velocity Multi Class path Resource Loader");
			//velocityEngine.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
			// 使用自定义的 ResourceLoader
			velocityEngine.setProperty("class.resource.loader.class", MultiClasspathResourceLoader.class.getName());
			velocityEngine.setProperty("class.resource.loader.path", config.getTemplateRoot());
		}else{
			// 从文件系统加载模板
			velocityEngine.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, config.getTemplateRoot());
		}
		/** 允许 set null */
		velocityEngine.setProperty(VelocityEngine.SET_NULL_ALLOWED, "true");
		velocityEngine.setProperty(VelocityEngine.INPUT_ENCODING, "UTF-8");
		velocityEngine.setProperty(VelocityEngine.OUTPUT_ENCODING, "UTF-8");
		
		velocityContext = new VelocityContext(context.getContext());
		// 字符串转义工具
		velocityContext.put(VELOCITY_ESC, new EscapeTool());
		// 排序工具
		velocityContext.put(VELOCITY_SORTER, new SortTool());

		return this;
	}

	public Generator parseCommandLine(String[] args) {
		HelpFormatter formatter = new HelpFormatter();
		CommandLineParser parser = new DefaultParser();
		CommandLine cl = null;
		Options options = getOptions();
		if(!options.hasOption(HELP_OPTION)){
			options.addOption(HELP_OPTION, HELP_OPTION_LONG, false, HELP_OPTION_DESC);
		}		
		String formatstr = getCmdLineSyntax();
		boolean exit = false;
		try {
			// 处理Options和参数
			cl = parser.parse(getOptions(), args);
			if (!cl.hasOption(HELP_OPTION)) {
				if (cl.hasOption(DEFINE_OPTION)) {
					setSystemProperty(cl.getOptionValues(DEFINE_OPTION));
				}
				config = getGeneratorConfiguration();
				config.loadConfig(options, cl);
			} else {
				exit = true;
			}
		} catch (ParseException e) {
			logger.error(e.toString());
			exit = true;
		}
		if (exit) {
			options.addOption(HELP_OPTION, HELP_OPTION_LONG, false, HELP_OPTION_DESC);
			formatter.printHelp(formatstr, options); // 如果发生异常，则打印出帮助信息
			System.exit(1);
		}
		return this;
	}

	private void setSystemProperty(String[] properties) {
		for (int i = 0; i < properties.length; i += 2) {
			System.setProperty(properties[i], properties[i + 1]);
			logger.info("set property [{}]=[{}]", properties[i], properties[i + 1]);
		}
	}
	/**
	 * 返回代码写入类对象({@link CodeWriter}),子类可以重写此方法
	 */
	protected CodeWriter getCodeWriter(){
		return new JavaCodeWriter(config.getOutputLocation());
	}
	protected Context createEngineContext() {
		codeWriter = getCodeWriter();
		return Context
				.builder()
				.addProperty(CMD_CONFIG, config)
				.addProperty(CODE_WRITER, codeWriter)
				.addProperty(INCLUDE_FOLDER, config.getIncludeFolder())
				.addProperty(TEMPLATE_FOLDER, config.getTemplateFolder())
				.addProperty(
						GENERAED_BY,
						String.format("计算机生成代码(generated by automated tools %s @author guyadong)", this.getClass()
								.getSimpleName())).build();
	}

	/**
	 * 在对{@code taskFolder}进行执行生成任务前调用<br>
	 * 子类可以重写此方法对{@code context}进行修改
	 * @param context
	 * @param taskFolder
	 */
	protected void beforeGenerate(VelocityContext context, String taskFolder){		
	}
	/**
	 * 在对{@code taskFolder}进行执行生成任务后调用<br>
	 * 子类可以重写此方法对{@code context}进行修改
	 * @param context
	 * @param taskFolder
	 */
	protected void afterGenerate(VelocityContext context, String taskFolder){		
	}
	protected void beforeGenerateFile(VelocityContext context, String templateName){
		
	}
	protected void afterGenerateFile(VelocityContext context, String templateName){
		
	}
	/**
	 * 子类重写此方法返回{@link #multiGenerate()}方法需要的任务数据<br>
	 * key 模板子文件夹 <br>
	 * value schema对象列表
	 */
	protected Map<String, List<? extends AbstractSchema>> generateTask(){
		return Collections.emptyMap();
	}
	
}